官方 Demo:https://vueuse.org/core/useMouseInElement/#usemouseinelement
看到 Demo 中有 x、y、sourceType,完全就是用 Day 11 & Day 12 看到的 useMouse 所 return 出來的值。
// src/compositions/useMouseInElement.js
import { ref, watch } from 'vue'
import { defaultWindow, unrefElement } from '@/helper'
import { useMouse } from '@/compositions/useMouse'
import { useEventListener } from '@/compositions/useEventListener'
export function useMouseInElement(target, options = {}) {
const {
handleOutside = true,
window = defaultWindow,
} = options
const type = options.type || 'page'
const { x, y, sourceType } = useMouse(options)
const targetRef = ref(target ?? window?.document.body)
const elementX = ref(0)
const elementY = ref(0)
const elementPositionX = ref(0)
const elementPositionY = ref(0)
const elementHeight = ref(0)
const elementWidth = ref(0)
const isOutside = ref(true)
let stop = () => {}
if (window) {
stop = watch(
[targetRef, x, y],
() => {
const el = unrefElement(targetRef)
if (!el)
return
const {
left,
top,
width,
height,
} = el.getBoundingClientRect()
elementPositionX.value = left + (type === 'page' ? window.pageXOffset : 0)
elementPositionY.value = top + (type === 'page' ? window.pageYOffset : 0)
elementHeight.value = height
elementWidth.value = width
const elX = x.value - elementPositionX.value
const elY = y.value - elementPositionY.value
isOutside.value = width === 0 || height === 0
|| elX < 0 || elY < 0
|| elX > width || elY > height
if (handleOutside || !isOutside.value) {
elementX.value = elX
elementY.value = elY
}
},
{ immediate: true },
)
useEventListener(document, 'mouseleave', () => {
isOutside.value = true
})
}
return {
x,
y,
sourceType,
elementX,
elementY,
elementPositionX,
elementPositionY,
elementHeight,
elementWidth,
isOutside,
stop,
}
}
x
、y
:就是之前 useMouse 提到的那個 x、y,在 useMouseInElement
中也會到 useMouse
回傳的 x
、y
來進行計算。sourceType
: useMouse
回傳的東西,判斷當前觸發的事 mouse 或是 touch event。elementX
, elementY
:以 element 左上角為原點(0, 0),計算出原點與當前鼠標位置之間的距離。elementPositionX
, elementPositionY
:以 document 左上角(如果 type 是 page 的話)為原點(0, 0),計算出 element 與原點間的距離。elementWidth
、elementHeight
:element 的寬高。isOutside
:當前鼠標是否在 element 區域內。stop
:執行 stop 可以停止 useMouseInElement 內部 watch 的觀察。// src/compositions/useMouseInElement.js
export function useMouseInElement(target, options = {}) {
const {
handleOutside = true,
window = defaultWindow,
} = options
const type = options.type || 'page'
const { x, y, sourceType } = useMouse(options)
// ...略
stop = watch(
[targetRef, x, y],
() => {
// 相關計算都在這邊
},
{ immediate: true },
)
useEventListener(document, 'mouseleave', () => {
isOutside.value = true
})
}
target
:官網 Demo 那塊灰色區域,在這邊統一用 element 來代替。options.handleOutside
:option 跟 elementX、elementY 座標有關,如果是 true(預設值),鼠標移到 element 以外的地方,以會持續以 element 左上角為原點,計算並更新 elementX、elementY 的數值。options.type
: 'page' | 'client' | 'screen' | 'movement',預設為 pageoptions.window
options 的參數也會一起傳給 useMouse,這邊看一段 TS 的 code 會比較清楚
export function useMouseInElement(target, options = {}) {
// ...略
export interface MouseInElementOptions extends UseMouseOptions {
handleOutside?: boolean
}
// ...略
}
除了 handleOutside
,是 useMouseInElement 自己的,其他 option 都會是 useMouse 可以傳入的 option。而 useMouseInElement 實作有用到的 option 是我前面有提到的 target
、handleOutside
、options.type
、options.window
。
// src/compositions/useMouseInElement.js
export function useMouseInElement(target, options = {}) {
// ...略
// 相關計算都在這邊
// 這邊的 left, top 是透過 element.getBoundingClientRect() 取得
elementPositionX.value = left + (type === 'page' ? window.pageXOffset : 0)
elementPositionY.value = top + (type === 'page' ? window.pageYOffset : 0)
}
頁面卷軸滾動時,滾動距離必須加上 element 本身跟視窗左上角(0, 0)的距離,才能維持 element 跟 document 原點之間的距離。
window.pageXOffset 跟 window.scrollX 的差異基本上是沒有差異,mdn 說明 pageXOffset 是 scrollX 的別名,可以參考:https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX#notes
關於 type 判斷式 type === 'page'
,假設 type 是 client 的話,那原點是在視窗中可見區域的左上角位置,所以不會受到卷軸滾動影響,就不需要額外加上 pageOffset。
// src/compositions/useMouseInElement.js
export function useMouseInElement(target, options = {}) {
// ...略
// 相關計算都在這邊
const elX = x.value - elementPositionX.value
const elY = y.value - elementPositionY.value
if (handleOutside || !isOutside.value) {
elementX.value = elX
elementY.value = elY
}
}
以 x 座標、type 為 page 的情境來說,假設 element 的 x 座標(距離 document 最左側)為 300,而鼠標 x 座標(距離 document 最左側)為 100,那鼠標跟 element 的距離就是 100 - 300 = -200
// src/compositions/useMouseInElement.js
export function useMouseInElement(target, options = {}) {
// ...略
// 相關計算都在這邊
isOutside.value = width === 0 || height === 0
|| elX < 0 || elY < 0
|| elX > width || elY > height
useEventListener(document, 'mouseleave', () => {
isOutside.value = true
})
}
這裡有用到 Day 8 ~ Day 10 實作到的 useEventListener,
可以看到連對 document 的 mouseleave 事件監聽都有考慮進來,我來做的話大概會漏掉這個吧 XD
useMouseInElement 到這邊告一段落,主要都是距離的計算,明天開始會從 useDeviceOrientation 接著講~